package com.itheima;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;


import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;

/*
SAXReader解析xml文件数据
    1、读取xml数据
    SAXReader可以通过多种方式读取xml数据，并返回Document格式的对象。
    read()方法接收File，InputStream和URL等格式的参数来读取相应的xml数据。
    在代码里我演示了读取xml文档和xml格式的字符串两种方式。
    当然，字符串要根据相应的编码转成输入流才能被SAXReader读取。

    2、解析xml数据
    读取到Document对象后，我们使用getRootElement()方法获取根元素，返回的是一个Element对象。

    3、获取子元素数据
    获取根元素后，便可以一层一层的去获取他的子元素信息。
    如果知道子元素的标签名称，便可以直接调用element("name")方法获取该子元素。
    如果不知道子元素的名称，或者想直接获取该元素下的全部子元素，
    可以调用elements()方法获取一个包括全部元素的list，然后进行下一步的处理。
    4、输出元素信息
    调用getName()方法获取当前元素的元素名，
    attributeValue()获取属性名。
    如果当前元素没有子元素，则调用getText()方法获取元素值。
 */

public class SpringApplicationContext {

    //解析的配置文件名
    private String fileName;
    //声明一个Map类型的全局变量，用来模拟Spring的容器
    //String代表bean的id（唯一标识符），Object即需要创建的对象
    private Map<String,Object> map = new HashMap<String, Object>();


    //使用构造函数传递需要解析的配置文件名称

    public SpringApplicationContext(String fileName) {
        this.fileName = fileName;
        // 在创建对象时，就需要解析xml文件，所以这里就要调用parseXml方法
        parseXml();
    }


    //解析配置文件的方法（用dom4j技术解析xml文件),并根据解析结果将获取的对象存入到Map集合中
    public void parseXml() {
        try {
            // 1.创建SAXReader对象
            /*
                SAXReader是dom4j的一个类，存在多个重载的read方法读取xml数据，并返回Document格式的对象
                Document是一个接口，提供了对文档数据的基本访问方法
                read(File file)
                read(URL url)
                read(InputStream in)
                read(Reader reader)
                ......
             */
            SAXReader reader = new SAXReader();
            //2.从xml文件获取数据，拿到document对象
            /*
                需要将xml文件转为输入流交给reader读取、
                在ClassLoader类里，有读取指定资源的输入流的方法:
                    public InputStream getResourceAsStream(String name)
                类-》类加载器-》得到classLoader对象，调取方法获得输入流
             */
            InputStream is = this.getClass().getClassLoader().getResourceAsStream(fileName);
            Document documentXml = reader.read(is);
            // 3.通过document对象，获取根节点，即<beans>
            Element root = documentXml.getRootElement();
            // 4.通过根节点，查找所有子节点bean，这些节点都是element类型的对象
            List<Element> beanList = root.elements();
            // 5.在遍历取出所有bean元素前，为了防止集合为空导致空指针异常，需要判断beanlist是否为空
            /*
                    beanList != null , 确保集合存在
                    beanList.size() > 0 ， 确保集合内的元素不是null
             */
            if (beanList != null && beanList.size() > 0) {
                for (Element bean : beanList) {
                    //6.获取bean标签内的id（唯一标识符）和class属性值（需要创建的对象的类的全路径）
                    String id = bean.attributeValue("id");
                    String className = bean.attributeValue("class");

                    //7.通过反射来创建bean对象以及为对象的属性进行赋值
                    Class<?> clazz = Class.forName(className);//这里不需要双引号
                    Object obj = clazz.newInstance();

                    //8. 因为存在依赖注入，所以继续读取bean标签下的子标签property的内容
                    List<Element> propertyList = bean.elements();
                    if (propertyList != null && propertyList.size() > 0) {
                        for (Element property : propertyList) {
                            //9.获取<property>标签的name属性
                            String name = property.attributeValue("name");
                            //10.根据name获取类中的属性值
                            // 取到property中name属性的值以后，用getDeclaredField方法返回这个类中的指定字段（即name）的Field对象
                            // 因为大多数类的成员变量都是private修饰的，所以需要暴力反射才能取到值
                            Field field = clazz.getDeclaredField(name);
                            field.setAccessible(true);
                            //11.获取<property>标签的value属性
                            String value = property.attributeValue("value");
                            //12.如果value不为空，且不是空字段，那么就可以给value赋值
                            if(value!=null && !("".equals(value))) {
                                /*
                                       Field类中的方法：
                                       void         set(Object obj, Object value)
                                       向对象的这个Field属性设置新值value
                                 */
                                //即给obj这个对象的filed字段赋值
                                field.set(obj,value);
                            }
                        }
                    }
                    //13.最后将对象存到集合里
                    map.put(id, obj);
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    //定义方法，根据用户传入的beanId(唯一标识符）来从Map集合中获取bean对象
    //因为不知道bean对象是什么类型，所以选择用定义泛型T接收
    public <T> T getBean(String beanId) {
        return (T) map.get(beanId);
    }
}


